package hello;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.function.Consumer;
import java.util.stream.IntStream;

/**
 * 测试几种文件对不同大小文件的读取效率
 * @author ben
 * @date 2021-09-14 21:12:02 CST
 */
public class TestFileRead {

	public static Consumer<Object> cs = System.out::println;

	// 2.6GB 文件
	// IO方式：读取数据为负数——超过整数最大值了
	// MappedByteBuffer方式 发生异常：java.lang.IllegalArgumentException: Size exceeds Integer.MAX_VALUE
	static String fn0 = "d:\\file0";
	// 1GB 文件
	static String fn1 = "d:\\file1";
	// 177MB 文件
	static String fn2 = "d:\\file2";
	// 2KB 文件
	static String fn3 = "d:\\file3";
	
	// 值约大，传统IO读取的效率越高（单线程测试情况下）
	// 分别测试 1024、10*1024、1024*1024
	static int buffSize = 1024;
	
	public static void main(String[] args) {
		final int count = 1;
		// 修改为不同值
		final String fn = fn0;
		cs.accept("test start...");
		long t1 = System.currentTimeMillis();
		IntStream.range(0, count).forEach(item->{
			readByIo(fn);
		});
		long t2 = System.currentTimeMillis();
		cs.accept("t2-t1=" + (t2 - t1) + "毫秒");
		
		IntStream.range(0, count).forEach(item->{
			readByNio(fn);
		});
		long t3 = System.currentTimeMillis();
		cs.accept("t3-t2=" + (t3 - t2) + "毫秒");
		
		IntStream.range(0, count).forEach(item->{
			readByMapped(fn);
		});
		long t4 = System.currentTimeMillis();
		cs.accept("t4-t3=" + (t4 - t3) + "毫秒");
		
		cs.accept("test end...");
	}

	/**
	 * 传统IO方式读取
	 * @author ben
	 * @date 2021-09-14 18:52:48 CST
	 */
	private static void readByIo(String fn) {
		File file = new File(fn);
		
		try (FileInputStream fis = new FileInputStream(file)) {
			int readLen = 0;
			// 需要更改为 长整型
			long size = 0;
			
			byte[] barr = new byte[buffSize];
			
			while((readLen = fis.read(barr)) > 0) {
				size += readLen;
			}
			
			cs.accept("io read file size=" + size + " bytes");
		} catch (FileNotFoundException e) {
			cs.accept("e=" + e.getMessage());
		} catch (IOException e) {
			cs.accept("e=" + e.getMessage());
		}
	}

	/**
	 * NIO方式读取
	 * @author ben
	 * @date 2021-09-14 21:14:36 CST
	 * @param fn
	 */
	private static void readByNio(String fn) {
		File file = new File(fn);
		try (FileInputStream fis = new FileInputStream(file);
				FileChannel fc = fis.getChannel()) {
			long size = 0L;
			
			int cap = buffSize;
			ByteBuffer bf = ByteBuffer.allocate(cap);
			int readLen = -1;
			while ((readLen = fc.read(bf)) != -1) {
				
				bf.clear();
				
				size += readLen;
			}
			cs.accept("nio read file size=" + size + " bytes");
		} catch (FileNotFoundException e) {
			cs.accept("e=" + e.getMessage());
		} catch (IOException e) {
			cs.accept("e=" + e.getMessage());
		}
	}
	
	/**
	 * MappedByteBuffer方式
	 * 占用更多内存——Eden Space
	 * @author ben
	 * @date 2021-09-14 21:15:22 CST
	 * @param fn
	 */
	private static void readByMapped(String fn) {
		// 2GB
		// 错误写法
//		final long maxFileSize = 2 * 1024 * 1024 * 1024;
		// 任何一个数字要加L结尾，还要减去1，不能过Integer.MAX_VALUE
		final long maxFileSize = 2 * 1024 * 1024 * 1024L - 1L;
		try (RandomAccessFile file = new RandomAccessFile(fn, "rw");) {
			FileChannel fc = file.getChannel();
			long fsize = file.length();
			int mod = (int) (fsize % maxFileSize);
			int div = (int) (fsize / maxFileSize);
			int mbblen = mod == 0 ? div : (div + 1);
			MappedByteBuffer[] mbb = new MappedByteBuffer[mbblen];
			for (int i=0; i<mbblen; i++) {
				if ((i+1) < mbblen) {
					mbb[i] = fc.map(MapMode.READ_ONLY, i * maxFileSize, maxFileSize);
				} else {
					mbb[i] = fc.map(MapMode.READ_ONLY, i * maxFileSize, file.length() - (i * maxFileSize));
				}
			}
			
			long fileSize = 0;
			for (int i=0; i<mbblen; i++) {
				MappedByteBuffer buff = mbb[i];

				while (buff.hasRemaining()) {
					int size = buffSize <= buff.remaining() ? buffSize : buff.remaining();
					byte[] dst = new byte[size];
					buff.get(dst);
					
					// 计算文件长度
					fileSize += dst.length;
				}
			}
			cs.accept("readByMapped read file size=" + fileSize + " bytes");
		} catch (FileNotFoundException e) {
			cs.accept("e=" + e.getMessage());
		} catch (IOException e) {
			cs.accept("e=" + e.getMessage());
		}
	}
	
}
